library mozilla;

include <core.vhs>
include <netutil.vhs>

import core::*;
import core::Pattern::*;
import core::Logging::*;
import core::JobEngine::*;
import netutil::*;

const MFSA_ENTRY_REF = @'href="(?:/security/announce/)?(\d+/mfsa\d+-\d+.html)"[^>]*>\s*MFSA\s+([^<]*)</a>([^<]*)<br>';
//const MFSA_ENTRY_REF = @'href="(?:/security/announce/)?(\d+/mfsa2007-02.html)"\s+class="(.+?)">\s*MFSA\s+(.+?)</a>(.+?)<br>';
const MFSA_ENTRY_GROUPS = struct { URL = 1, ID=2, Title = 3 };

const ADVISORY = 0;
const FIXED_RELEASE = 1;
const BUG_LINK = 2;
const CVE_LINK = 3;
const BUGZILLA_CVE = 4;

pattern<DataPart> MozillaAdvisories($dataPattern=MFSA_ENTRY_REF, $groups=MFSA_ENTRY_GROUPS, $maxOccur=INFINITY, 
									$mismatchedException=true, $outfunc = null, $outfunc_arg = null) {
	queueJob(function ProcessAdvisories(url, outfunc, advisory_entry, ...) {
		log("Process url: %s", url);
		var filename = download( "http://www.mozilla.org/security/announce/" + url, SEC_PER_MONTH);
		MozillaAdvisory(input = new SequenceStream(filename, 4096), outfunc=outfunc, outfunc_arg=outfunc_arg, advisory_entry=advisory_entry);
	}, url = this.URL, outfunc = outfunc, outfunc_arg=outfunc_arg, advisory_entry=this);
}

pattern MozillaAdvisory($outfunc, $outfunc_arg, $advisory_entry) {
	if (AdvisoryHeader(outfunc = outfunc, outfunc_arg=outfunc_arg, advisory_entry=advisory_entry))
		AdvisoryReference(outfunc = outfunc, outfunc_arg=outfunc_arg, advisory_entry=advisory_entry);
	else
		log('...not a Firefox advisory');
}

const ANNOUNCED_DATE = @'(?:Announced|Date):</span>(.+?)<br>';
const FIXED_RELEASES = @"Fixed in:</span>|Product:.+?Firefox \d+(.\d+)*";

pattern AdvisoryHeader($outfunc, $outfunc_arg, $advisory_entry) {
	var disclosureDate = "";
	if (!isMatch(this.LocalInput, FIXED_RELEASES))
		return false;
	log('...header');
	if (isMatch(this.LocalInput, ANNOUNCED_DATE)) {
		var date = locate(ANNOUNCED_DATE);		
		disclosureDate = Runtime.formatDate(Runtime.asDate(date[1].replace(",","").trim(), 'MMMMM dd yyyy'), 'yyyy-MM-dd');
		log('......announced on %s', disclosureDate);
	}
	else 
		log('......announced_date_missing');	
		
	locate(FIXED_RELEASES);	
	locate_end('</p>');	
	
	var count = 0;
	var id = advisory_entry.ID.trim();
	
	var p = pattern<DataPart> FixedReleases($dataPattern=@'Firefox (\d+(\.\d+)*)', $minOccur=0, $maxOccur=INFINITY, $groups= struct { Release = 1}) {
		if (this.Release != '1.0' && this.Release != '0.9') {
			count++;
			var rev = this.Release.split(@'\.');
			var majorRelease = rev[0] + '.' + if (length(rev) > 1) rev[1] else '0';
			log("......found %d fixed releases: %s (%s)", count, majorRelease, this.Release);
			outfunc?(struct {Type=FIXED_RELEASE, ID = id, Release = this.Release, MajorRelease = majorRelease}, outfunc_arg);
		}
	};
	p();
	if (count > 0)
		outfunc?(struct { Type = ADVISORY, 
						ID = id, 
						DisclosureDate = disclosureDate, 
						Title = advisory_entry.Title.trim() }
				, outfunc_arg);
}

const MFSA_REF_BUGZILLA = @'href="https://bugzilla.mozilla.org/(?:show_bug|buglist)\.cgi\?(?:bug_)?id=(\d+((?:,|%2C)\d+)*)"';
//const MFSA_REF_CVE = @'http://cve.mitre.org/cgi-bin/cvename.cgi\?name=(?:CAN|CVE)-(.+?)"'; 
const MFSA_REF_CVE = @'href="http://.+?(?:CAN|CVE)-(.+?)"'; 
const LI = @'<li>(.+?)</li>';

const CONFIDENCE_MEDIUM = 2;

const REFERENCES = @'<h3>References</h3>';
const REFERENCE_SECTION = @'<p>.+?References.+?</p>';

pattern AdvisoryReference($outfunc, $outfunc_arg, $advisory_entry) {
	SingleReferenceSection(outfunc=outfunc, outfunc_arg=outfunc_arg, advisory_entry=advisory_entry);
	/*if (!MultipleReferenceSection(outfunc=outfunc, outfunc_arg=outfunc_arg, advisory_entry=advisory_entry)) {
		log('...No reference found');
		Runtime.stop();		
		locate_end('</div>');
		log('input: %s', this.LocalInput);
		ReferenceBody(outfunc=outfunc, outfunc_arg=outfunc_arg, advisory_entry=advisory_entry);
	}*/
}

pattern SingleReferenceSection($minOccur=0, $outfunc, $outfunc_arg, $advisory_entry) {
	//locate(REFERENCES);
	locate_end('</div>');
	log('...reference found');
	ReferenceBody(outfunc=outfunc, outfunc_arg=outfunc_arg, advisory_entry=advisory_entry);
}

pattern MultipleReferenceSection($minOccur=0, $maxOccur=INFINITY, $outfunc, $outfunc_arg, $advisory_entry) {
	locate(REFERENCE_SECTION, excludeMatchedText=true);
	locate_end('</div>');
	//locate('<p');
	//locate_end('</p>');
	log('...reference section found');
	//Runtime.stop();
	//log("input = %s", this.LocalInput);
	ReferenceBody(outfunc=outfunc, outfunc_arg=outfunc_arg, advisory_entry=advisory_entry);
	return true;
}

pattern ReferenceBody($outfunc, $outfunc_arg, $advisory_entry) {
	// first extract all bugs
	var bugs = MozillaBugzillaLink(updateParentPosition=false);
	if (bugs.size() > 0)
		outfunc?(struct { Type=BUG_LINK, ID=advisory_entry.ID, Bugs = bugs }, outfunc_arg);
	// next, extract all CVEs
	var cves = CveLink(updateParentPosition=false);
	if (cves.size() > 0)
		outfunc?(struct { Type=CVE_LINK, ID=advisory_entry.ID, CVEs = cves }, outfunc_arg);
	// finally, find the potential links
	ULreference(outfunc=outfunc, outfunc_arg=outfunc_arg, advisory_entry=advisory_entry);
}

pattern ULreference($minOccur=0, $maxOccur=INFINITY, $outfunc, $outfunc_arg, $advisory_entry) {
	locate('<ul>');
	locate_end('</ul>');
	var firstLI = ListItem();
	var bugs;
	var cves;
	var linkPattern='';
	if (isMatch(firstLI, MFSA_REF_BUGZILLA)) {
		bugs = MozillaBugzillaLink();
		cves = CveLink();
		linkPattern = 'UL: Bug+ Cve*';
	}
	else {
		cves = CveLink();
		bugs = MozillaBugzillaLink();
		linkPattern = 'UL: Cve* Bug+';
	}
	if (cves.size() > 0)
		outfunc?(struct { Type=BUGZILLA_CVE, ID=advisory_entry.ID, Bugs = bugs, CVEs = cves, Confidence = CONFIDENCE_MEDIUM, LinkPattern = linkPattern }, outfunc_arg)
}

pattern<DataPart> ListItem($dataPattern= LI, $updateParentPosition=false) return this[1]; 

pattern<DataPart> MozillaBugzillaLink($dataPattern= MFSA_REF_BUGZILLA, $maxOccur=INFINITY, $returnLastMatch=false) return this[1].replace('%2C', ',');

pattern<DataPart> CveLink($dataPattern= MFSA_REF_CVE, $minOccur=0, $maxOccur=INFINITY, $returnLastMatch=false) return this[1];

